package io.openkunlun.javadsl.serialization;

import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.google.protobuf.MessageLite;

import java.io.IOException;
import java.lang.reflect.Method;

public abstract class JacksonSerializer implements DaprSerializer {

    private final ObjectMapper mapper;

    /**
     * @param mapper
     */
    protected JacksonSerializer(ObjectMapper mapper) {
        this.mapper = mapper;
    }

    @Override
    public byte[] serialize(Object o) throws IOException {

        if (o instanceof byte[]) {
            return (byte[]) o;
        }
        if (o instanceof MessageLite) {
            return ((MessageLite) o).toByteArray();
        }
        return mapper.writeValueAsBytes(o);
    }

    @Override
    public <T> byte[] serialize(Object o, DaprObject<T> type) throws IOException {

        if (o instanceof byte[]) {
            return (byte[]) o;
        }
        if (o instanceof MessageLite) {
            return ((MessageLite) o).toByteArray();
        }

        JavaType javaType = mapper.constructType(type.getType());
        ObjectWriter writer = mapper.writerFor(javaType);
        return writer.writeValueAsBytes(o);
    }

    @Override
    public <T> T deserialize(byte[] data, Class<T> type) throws IOException {

        if (data == null) {
            return null;
        }

        JavaType javaType = mapper.constructType(type);
        return deserialize(data, javaType);
    }

    @Override
    public <T> T deserialize(byte[] data, DaprObject<T> type) throws IOException {

        if (data == null) {
            return null;
        }

        JavaType javaType = mapper.constructType(type.getType());
        return deserialize(data, javaType);
    }

    @SuppressWarnings({"PrimitiveArrayArgumentToVarargsMethod", "unchecked"})
    protected  <T> T deserialize(byte[] data, JavaType javaType) throws IOException {

        if (javaType.hasRawClass(Void.class)) {
            return null;
        }

        if (javaType.hasRawClass(byte[].class)) {
            return (T) data;
        }

        if (javaType.isTypeOrSubTypeOf(MessageLite.class)) {
            try {
                Method method = javaType.getRawClass().getDeclaredMethod("parseFrom", byte[].class);
                return (T) method.invoke(null, data);
            } catch (NoSuchMethodException e) {
                // ignore
            } catch (Exception e) {
                throw new IOException(e);
            }
        }

        if (data.length == 0) {
            return null;
        }
        return mapper.readValue(data, javaType);
    }

    final ObjectMapper getMapper() {
        return mapper;
    }
}
